﻿// PortableMediator
// (c) Lance Wynn - http://PortableMediator.codeplex.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace PortableMediator
{
    public class MediatorFunc<TResult> : IWeakAction
    {
        private WeakReference _referenceObject;
        private MethodInfo _methodInfo;

        public MediatorFunc(object target, MethodInfo methodInfo)
        {
            _referenceObject = new WeakReference(target, true);
            _methodInfo = methodInfo;
            ////This checks if the return type of the method is a Task<T> vs a Void Task, so when invoked we can easily determine how to handle the results.
            if (typeof(Task).GetTypeInfo().IsAssignableFrom(_methodInfo.ReturnType.GetTypeInfo()) && _methodInfo.ReturnType.Name.Contains("`1"))
            {
                MethodReturnsResultTask = true;
            }

        }

        private bool MethodReturnsResultTask { get; set; }

        public async Task Invoke(object param)
        {
            await Invoke(param, null);
        }

        public async Task Invoke(object param, object callback)
        {
            await Task.Run(() =>
            {
                if (this.IsAlive())
                {
                    var target = this._referenceObject.Target;
                    var methodResult = _methodInfo.Invoke(target, new object[] { param });
                    
                    if (MethodReturnsResultTask)
                    {
                        if(!((Task)methodResult).IsCompleted)
                        {
                            ((Task)methodResult).Wait(); ////wait for the task to complete.
                        }
                        dynamic dynamicTask = methodResult;
                        var dynamicResult = dynamicTask.Result;
                        
                        if (callback != null)
                        {
                            dynamic dynamicCallback = callback;
                            try
                            {
                                dynamicCallback.Invoke(dynamicResult);
                            }
                            catch (RuntimeBinderException)
                            {
                                //this exception occurs if the type is wrong, and I will ignore it.
                            }
                        }
                    }
                    else
                    {
                        TResult result = (TResult)methodResult;
                        
                        if (callback != null)
                        {
                            try
                            {
                                ((Action<TResult>)callback).Invoke((TResult)result);
                            }
                            catch(InvalidCastException)
                            {
                                //this exception occurs if the type is wrong, and I will ignore it.
                            }
                        }
                    }
                }
            });
        }

        public bool IsAlive()
        {
            return _referenceObject.IsAlive && !this._isKilled;
        }

        public async Task<Type> GetTargetType()
        {
            return await Task.Run(() =>
            {
                if (!_referenceObject.IsAlive)
                {
                    return typeof(object);
                }
                return _referenceObject.Target.GetType();
            });
        }

        public async Task<Type> GetParamType()
        {
            return await Task.Run(() =>
            {
                return _methodInfo.GetParameters()[0].ParameterType;
            });
        }

        private bool _isKilled;

        public void Kill()
        {
            this._isKilled = true;
        }
    }
}
